package block

import (
	"bytes"
	"crypto/ecdsa"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"gaojunyublockchain/utils"
	"log"
	"math/big"
	"net/http"
	"os"
	"strings"
	"sync"
	"time"

	"github.com/fatih/color"
)

var MINING_DIFFICULT = 0x80000

const MINING_ACCOUNT_ADDRESS = "GAOJUNYU BLOCKCHAIN"
const MINING_REWARD = 5
const MINING_TIMER_SEC = 10
const (
	//以下参数可以添加到启动参数
	BLOCKCHAIN_PORT_RANGE_START      = 5000
	BLOCKCHAIN_PORT_RANGE_END        = 5003
	NEIGHBOR_IP_RANGE_START          = 0
	NEIGHBOR_IP_RANGE_END            = 0
	BLOCKCHIN_NEIGHBOR_SYNC_TIME_SEC = 20
)

type Block struct {
	nonce        *big.Int
	previousHash [32]byte
	number       *big.Int
	timestamp    uint64
	transactions []*Transaction
	hash         [32]byte
	size         uint16
}

type BlockPrint struct {
	Timestamp    uint64         `json:"timestamp"`
	Nonce        *big.Int       `json:"nonce"`
	PreviousHash string         `json:"previous_hash"`
	Size         uint16         `json:"size"`
	Hash         string         `json:"hash"`
	Transactions []*Transaction `json:"transactions"`
}

func NewBlock(nonce int, previousHash [32]byte, txs []*Transaction, number *big.Int) *Block {
	b := new(Block)
	b.timestamp = uint64(time.Now().UnixNano())
	b.nonce = big.NewInt(int64(nonce))
	b.previousHash = previousHash
	b.transactions = txs
	b.size = uint16(len(txs))
	b.hash = b.Hash()
	b.number = number

	//将每次新增区块写入txt文件
	writeBlockDataToTXTFile(b)

	// 将 transactions新增 写入单独的文件
	writeTransactionsToTXTFile(b.transactions)

	return b
}

// 将新增区块信息打印在txt中
func writeBlockDataToTXTFile(block *Block) {
	// 打开文件，使用追加模式
	file, err := os.OpenFile("区块信息存储.txt", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
	if err != nil {
		fmt.Println("无法打开文件:", err)
		return
	}
	defer file.Close()

	currentTime := time.Now().Format("2006/01/02 15:04:05")

	separator := fmt.Sprintf("-------------- 区块 %s --------------", currentTime)

	// 打印并写入字段值
	_, err = fmt.Fprintf(file, "Timestamp: %d\n", block.timestamp)
	if err != nil {
		fmt.Println("写入文件失败:", err)
		return
	}

	_, err = fmt.Fprintf(file, "Nonce: %s\n", block.nonce.String())
	if err != nil {
		fmt.Println("写入文件失败:", err)
		return
	}

	_, err = fmt.Fprintf(file, "PreviousHash: %x\n", block.previousHash)
	if err != nil {
		fmt.Println("写入文件失败:", err)
		return
	}

	_, err = fmt.Fprintf(file, "Number: %s\n", block.number.String())
	if err != nil {
		fmt.Println("写入文件失败:", err)
		return
	}

	_, err = fmt.Fprintf(file, "Hash: %x\n", block.hash)
	if err != nil {
		fmt.Println("写入文件失败:", err)
		return
	}

	_, err = fmt.Fprintf(file, "Size: %d\n", block.size)
	if err != nil {
		fmt.Println("写入文件失败:", err)
		return
	}
	fmt.Fprintln(file, separator)
	fmt.Println("写入文件成功")
}

// 将 新增的transactions 追加写入文件
func writeTransactionsToTXTFile(transactions []*Transaction) {
	// 打开文件，使用追加模式
	file, err := os.OpenFile("交易存储.txt", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
	if err != nil {
		fmt.Println("无法打开文件:", err)
		return
	}
	defer file.Close()

	currentTime := time.Now().Format("2006/01/02 15:04:05")
	separator := fmt.Sprintf("-------------- 区块 %s --------------", currentTime)

	// 打印并写入 transactions
	for _, tx := range transactions {
		_, err = fmt.Fprintf(file, "Transaction: %v\n", tx)
		if err != nil {
			fmt.Println("写入文件失败:", err)
			return
		}
	}
	fmt.Fprintln(file, separator)
}

func (b *Block) PreviousHash() [32]byte {
	return b.previousHash
}

func getSize(tp []*Transaction) uint64 {
	_size := 0
	for _, t := range tp {
		m, _ := t.MarshalJSON()
		_size = _size + len(m)
	}
	return uint64(_size)
}

func (b *Block) Nonce() *big.Int {
	return b.nonce
}

func (b *Block) Transactions() []*Transaction {
	return b.transactions
}

func (b *Block) Number() *big.Int {
	return b.number
}

func (b *Block) Print() string {
	blockPrint := BlockPrint{
		Timestamp:    b.timestamp,
		Nonce:        b.nonce,
		PreviousHash: fmt.Sprintf("%x", b.previousHash),
		Size:         b.size,
		Hash:         fmt.Sprintf("%x", b.hash),
		Transactions: b.transactions,
	}

	blockJSON, err := json.Marshal(blockPrint)
	if err != nil {
		// 处理错误
		return ""
	}

	return string(blockJSON)
}

type Blockchain struct {
	transactionPool   []*Transaction
	chain             []*Block
	blockchainAddress string
	port              uint16
	mux               sync.Mutex

	neighbors    []string
	muxNeighbors sync.Mutex
}

// 新建一条链的第一个区块
// NewBlockchain(blockchainAddress string) *Blockchain
// 函数定义了一个创建区块链的方法，它接收一个字符串类型的参数 blockchainAddress，
// 它返回一个区块链类型的指针。在函数内部，它创建一个区块链对象并为其设置地址，
// 然后创建一个创世块并将其添加到区块链中，最后返回区块链对象。
func NewBlockchain(blockchainAddress string, port uint16) *Blockchain {
	bc := new(Blockchain)
	b := &Block{}
	bc.CreateBlock(0, b.Hash(), big.NewInt(0)) //创世纪块
	bc.blockchainAddress = blockchainAddress

	bc.port = port
	return bc
}

func (bc *Blockchain) Chain() []*Block {
	return bc.chain
}

func (bc *Blockchain) Run() {
	bc.StartSyncNeighbors()
	bc.ResolveConflicts()
	bc.StartMining()
}

func (bc *Blockchain) SetNeighbors() {
	bc.neighbors = utils.FindNeighbors(
		utils.GetHost(), bc.port,
		NEIGHBOR_IP_RANGE_START, NEIGHBOR_IP_RANGE_END,
		BLOCKCHAIN_PORT_RANGE_START, BLOCKCHAIN_PORT_RANGE_END)

	color.Blue("邻居节点：%v", bc.neighbors)
}

func (bc *Blockchain) SyncNeighbors() {
	bc.muxNeighbors.Lock()
	defer bc.muxNeighbors.Unlock()
	bc.SetNeighbors()
}

func (bc *Blockchain) StartSyncNeighbors() {
	bc.SyncNeighbors()
	_ = time.AfterFunc(time.Second*BLOCKCHIN_NEIGHBOR_SYNC_TIME_SEC, bc.StartSyncNeighbors)
}

func (bc *Blockchain) TransactionPool() []*Transaction {
	return bc.transactionPool
}

func (bc *Blockchain) ClearTransactionPool() {
	bc.transactionPool = bc.transactionPool[:0]
}

func (bc *Blockchain) MarshalJSON() ([]byte, error) {
	return json.Marshal(struct {
		Blocks []*Block `json:"chains"`
	}{
		Blocks: bc.chain,
	})
}

func (bc *Blockchain) UnmarshalJSON(data []byte) error {
	v := &struct {
		Blocks *[]*Block `json:"chain"`
	}{
		Blocks: &bc.chain,
	}
	if err := json.Unmarshal(data, &v); err != nil {
		return err
	}
	return nil
}

// (bc *Blockchain) CreateBlock(nonce int, previousHash [32]byte) *Block
//  函数是在区块链上创建新的区块，它接收两个参数：一个int类型的nonce和一个字节数组类型的 previousHash，
//  返回一个区块类型的指针。在函数内部，它使用传入的参数来创建一个新的区块，
//  然后将该区块添加到区块链的链上，并清空交易池。

func (bc *Blockchain) CreateBlock(nonce int, previousHash [32]byte, number *big.Int) *Block {
	b := NewBlock(nonce, previousHash, bc.transactionPool, number)
	bc.chain = append(bc.chain, b)
	bc.transactionPool = []*Transaction{}
	return b
}

func (bc *Blockchain) Print() {
	for i, block := range bc.chain {
		color.Green("%s BLOCK %d %s\n", strings.Repeat("=", 25), i, strings.Repeat("=", 25))
		block.Print()
	}
	color.Yellow("%s\n\n\n", strings.Repeat("*", 50))
}

func (b *Block) Hash() [32]byte {
	m, _ := json.Marshal(b)
	return sha256.Sum256([]byte(m))
}

func (b *Block) MarshalJSON() ([]byte, error) {

	return json.Marshal(struct {
		Number       *big.Int       `json:"number"`
		Timestamp    uint64         `json:"timestamp"`
		Nonce        *big.Int       `json:"nonce"`
		PreviousHash string         `json:"previous_hash"`
		Size         uint16         `json:"size"`
		Hash         string         `json:"hash"`
		Transactions []*Transaction `json:"transactions"`
	}{
		Number:       b.number,
		Timestamp:    b.timestamp,
		Nonce:        b.nonce,
		PreviousHash: fmt.Sprintf("%x", b.previousHash),
		Size:         b.size,
		Hash:         fmt.Sprintf("%x", b.hash),
		Transactions: b.transactions,
	})
}

func (b *Block) UnmarshalJSON(data []byte) error {
	var previousHash string
	v := &struct {
		Number       **big.Int       `json:"number"`
		Timestamp    *uint64         `json:"timestamp"`
		Nonce        **big.Int       `json:"nonce"`
		PreviousHash *string         `json:"previous_hash"`
		Size         *uint16         `json:"size"`
		Hash         *[32]byte       `json:"hash"`
		Transactions *[]*Transaction `json:"transactions"`
	}{
		Number:       &b.number,
		Timestamp:    &b.timestamp,
		Nonce:        &b.nonce,
		PreviousHash: &previousHash,
		Size:         &b.size,
		Hash:         &b.hash,
		Transactions: &b.transactions,
	}
	if err := json.Unmarshal(data, &v); err != nil {
		return err
	}
	ph, _ := hex.DecodeString(*v.PreviousHash)
	copy(b.previousHash[:], ph[:32])
	return nil
}

func (bc *Blockchain) LastBlock() *Block {
	return bc.chain[len(bc.chain)-1]
}

func (bc *Blockchain) CreateTransaction(sender string, recipient string, value uint64,
	senderPublicKey *ecdsa.PublicKey, s *utils.Signature) bool {
	isTransacted := bc.AddTransaction(sender, recipient, int64(value), senderPublicKey, s)

	if isTransacted {
		for _, n := range bc.neighbors {
			publicKeyStr := fmt.Sprintf("%064x%064x", senderPublicKey.X.Bytes(),
				senderPublicKey.Y.Bytes())
			signatureStr := s.String()
			bt := &TransactionRequest{
				&sender, &recipient, &publicKeyStr, &value, &signatureStr}
			m, _ := json.Marshal(bt)
			buf := bytes.NewBuffer(m)
			endpoint := fmt.Sprintf("http://%s/transactions", n)
			client := &http.Client{}
			req, _ := http.NewRequest("PUT", endpoint, buf)
			resp, _ := client.Do(req)
			log.Printf("%v", resp)
		}
	}

	return isTransacted
}

func (bc *Blockchain) CopyTransactionPool() []*Transaction {
	transactions := make([]*Transaction, 0)
	for _, t := range bc.transactionPool {
		transactions = append(transactions,
			NewTransaction(t.senderAddress,
				t.receiveAddress,
				t.value))
	}
	return transactions
}

func bytesToBigInt(b [32]byte) *big.Int {
	bytes := b[:]
	result := new(big.Int).SetBytes(bytes)
	return result
}

func (bc *Blockchain) ValidProof(nonce int,
	previousHash [32]byte,
	transactions []*Transaction,
	difficulty int,
) bool {
	bigi_2 := big.NewInt(2)
	bigi_256 := big.NewInt(256)
	bigi_diff := big.NewInt(int64(difficulty))

	target := new(big.Int).Exp(bigi_2, bigi_256, nil)
	target = new(big.Int).Div(target, bigi_diff)

	//zeros := strings.Repeat("0", difficulty)
	tmpBlock := Block{nonce: big.NewInt(int64(nonce)), previousHash: previousHash, transactions: transactions, timestamp: uint64(time.Now().UnixNano())}
	result := bytesToBigInt(tmpBlock.Hash())
	//tmpHashStr := fmt.Sprintf("%x", tmpBlock.Hash())
	//return tmpHashStr[:difficulty] == zeros

	return target.Cmp(result) > 0
}

func (bc *Blockchain) ProofOfWork() int {
	transactions := bc.CopyTransactionPool() //选择交易？控制交易数量？
	previousHash := bc.LastBlock().Hash()
	nonce := 0
	begin := time.Now()

	if bc.getSpendTime(len(bc.chain)-1) < 3e+9 {
		MINING_DIFFICULT += 32
	} else {
		if MINING_DIFFICULT >= 130000 {
			MINING_DIFFICULT -= 32
		}
	}
	for !bc.ValidProof(nonce, previousHash, transactions, MINING_DIFFICULT) {
		nonce += 1
	}
	end := time.Now()

	log.Printf("POW spend Time:%f Second Diff%d:", end.Sub(begin).Seconds(), MINING_DIFFICULT)
	return nonce
}

func (bc *Blockchain) getSpendTime(blocknum int) uint64 {
	if blocknum == 0 {
		return 0
	}
	return uint64(bc.chain[blocknum].timestamp - bc.chain[blocknum-1].timestamp)
}

// 将交易池的交易打包
func (bc *Blockchain) Mining() bool {
	s := fmt.Sprintf("%f", MINING_REWARD)
	num1 := big.NewInt(0)
	num1.SetString(s, 10)
	bc.AddTransaction(MINING_ACCOUNT_ADDRESS, bc.blockchainAddress, MINING_REWARD, nil, nil) //因为是挖矿奖励，不用公钥和签名
	nonce := bc.ProofOfWork()
	previousHash := bc.LastBlock().Hash()
	var num = bc.LastBlock().number.Int64()
	bc.CreateBlock(nonce, previousHash, big.NewInt(num).Add(big.NewInt(num), big.NewInt(1)))
	color.Red("action=mining, status=success")
	return true
}

func (bc *Blockchain) CalculateTotalAmount(accountAddress string) uint64 {
	var totalAmount uint64 = 0
	for _, _chain := range bc.chain {
		for _, _tx := range _chain.transactions {
			if accountAddress == _tx.receiveAddress {
				totalAmount = totalAmount + uint64(_tx.value)
			}
			if accountAddress == _tx.senderAddress {
				totalAmount = totalAmount - uint64(_tx.value)
			}
		}
	}
	return totalAmount
}

func (bc *Blockchain) GetBalance(accountAddress string) uint64 {
	var AddressAmount uint64 = 0
	for _, _chain := range bc.chain {
		for _, _tx := range _chain.transactions {
			if accountAddress == _tx.receiveAddress {
				AddressAmount = AddressAmount + uint64(_tx.value)
			}
			if accountAddress == _tx.senderAddress {
				AddressAmount = AddressAmount - uint64(_tx.value)
			}
		}
	}
	return AddressAmount
}

func (bc *Blockchain) PrintBlockTimestamp() {
	fmt.Println("==========")
	for i, _ := range bc.chain {
		fmt.Printf("Block %d: %d \n", i, bc.getSpendTime(i))
	}
	fmt.Println("==========")
	fmt.Printf("AVG:%fs", float64(bc.Avg())/1e+9)
}
func (bc *Blockchain) Avg() uint64 {
	blocknum := len(bc.chain)
	return (bc.chain[blocknum-1].timestamp - bc.chain[0].timestamp) / uint64(blocknum)
}

func (bc *Blockchain) GetBlockByNumber(blockid uint64) (*Block, error) {
	var block *Block
	var err error
	for _, b := range bc.chain {
		if b.number.Uint64() == blockid {
			block = b
			break
		}
	}
	if block != nil {
		blockPrint := block.Print()
		fmt.Println(blockPrint)
	}
	return block, err
}

func (bc *Blockchain) GetBlockByHash(hash []byte) (*Block, error) {
	var block *Block
	var err error
	if len(hash) == 0 {
		err = errors.New("哈希不正确")
		return block, err
	}
	var byte32 [32]byte
	copy(byte32[:], hash)
	for _, block := range bc.chain {
		if block.hash == byte32 {
			block.Print()
			return block, err
		}
	}
	return block, err
}

func (bc *Blockchain) GetBlockByPreviousHash(previousHash []byte) (*Block, error) {
	var block *Block
	var err error
	if len(previousHash) == 0 {
		err = errors.New("交易哈希不正确")
		return block, err
	}
	var byte32 [32]byte
	copy(byte32[:], previousHash)
	for _, block := range bc.chain {
		if block.previousHash == byte32 {
			block.Print()
			return block, err
		}
	}
	return block, err
}
func (bc *Blockchain) StartMining() {
	bc.Mining()
	// 使用time.AfterFunc函数创建了一个定时器，它在指定的时间间隔后执行bc.StartMining函数（自己调用自己）。
	_ = time.AfterFunc(time.Second*MINING_TIMER_SEC, bc.StartMining)
	color.Yellow("minetime: %v\n", time.Now())
}

type AmountResponse struct {
	Amount uint64 `json:"amount"`
}

func (ar *AmountResponse) MarshalJSON() ([]byte, error) {
	return json.Marshal(struct {
		Amount uint64 `json:"amount"`
	}{
		Amount: ar.Amount,
	})
}

type Transaction struct {
	senderAddress  string
	receiveAddress string
	value          int64
}

func NewTransaction(sender string, receive string, value int64) *Transaction {
	t := Transaction{sender, receive, value}
	return &t
}

//Old AddTransaction
/*
func (bc *Blockchain) AddTransaction(sender string, receive string, value int64) {
	t := NewTransaction(sender, receive, value)
	bc.transactionPool = append(bc.transactionPool, t)
}
*/
func (bc *Blockchain) AddTransaction(
	sender string,
	recipient string,
	value int64,
	senderPublicKey *ecdsa.PublicKey,
	s *utils.Signature) bool {
	t := NewTransaction(sender, recipient, value)

	//如果是挖矿得到的奖励交易，不验证
	if sender == MINING_ACCOUNT_ADDRESS {
		bc.transactionPool = append(bc.transactionPool, t)
		return true
	}

	// 判断有没有足够的余额
	// log.Printf("transaction.go sender:%s  account=%d", sender, bc.CalculateTotalAmount(sender))
	// if bc.CalculateTotalAmount(sender) <= uint64(value) {
	// 	log.Printf("ERROR: %s ，你的钱包里没有足够的钱", sender)
	// 	return false
	// }

	//if bc.CalculateTotalAmount(sender) < uint64(value) {
	//	log.Printf("ERROR: %s ，你的钱包里没有足够的钱", sender)
	//	return false
	//}

	if bc.VerifyTransactionSignature(senderPublicKey, s, t) {

		bc.transactionPool = append(bc.transactionPool, t)
		return true
	} else {
		log.Println("ERROR: 验证交易")
	}
	return false

}

func (bc *Blockchain) VerifyTransactionSignature(
	senderPublicKey *ecdsa.PublicKey, s *utils.Signature, t *Transaction) bool {
	m, _ := json.Marshal(t)
	h := sha256.Sum256([]byte(m))
	return ecdsa.Verify(senderPublicKey, h[:], s.R, s.S)
}

func (t *Transaction) Print() string {
	output := ""
	color.Red("%s\n", strings.Repeat("~", 30))
	color.Cyan("发送地址             %s\n", t.senderAddress)
	color.Cyan("接受地址             %s\n", t.receiveAddress)
	color.Cyan("金额                 %d\n", t.value)
	return output
}

func (t *Transaction) MarshalJSON() ([]byte, error) {
	return json.Marshal(struct {
		Sender    string `json:"sender_blockchain_address"`
		Recipient string `json:"recipient_blockchain_address"`
		Value     int64  `json:"value"`
	}{
		Sender:    t.senderAddress,
		Recipient: t.receiveAddress,
		Value:     t.value,
	})
}

func (t *Transaction) UnmarshalJSON(data []byte) error {
	v := &struct {
		Sender    *string `json:"sender_blockchain_address"`
		Recipient *string `json:"recipient_blockchain_address"`
		Value     *int64  `json:"value"`
	}{
		Sender:    &t.senderAddress,
		Recipient: &t.receiveAddress,
		Value:     &t.value,
	}
	if err := json.Unmarshal(data, &v); err != nil {
		return err
	}
	return nil
}

func (bc *Blockchain) ValidChain(chain []*Block) bool {
	preBlock := chain[0]
	currentIndex := 1
	for currentIndex < len(chain) {
		b := chain[currentIndex]
		if b.previousHash != preBlock.Hash() {
			return false
		}

		if !bc.ValidProof(int(b.Nonce().Int64()), b.PreviousHash(), b.Transactions(), MINING_DIFFICULT) {
			return false
		}

		preBlock = b
		currentIndex += 1
	}
	return true
}

func (bc *Blockchain) ResolveConflicts() bool {
	var longestChain []*Block = nil
	maxLength := len(bc.chain)

	for _, n := range bc.neighbors {
		endpoint := fmt.Sprintf("http://%s/chain", n)
		resp, _ := http.Get(endpoint)
		if resp.StatusCode == 200 {
			var bcResp Blockchain
			decoder := json.NewDecoder(resp.Body)
			_ = decoder.Decode(&bcResp)

			chain := bcResp.Chain()

			if len(chain) > maxLength && bc.ValidChain(chain) {
				maxLength = len(chain)
				longestChain = chain
			}
		}
	}

	if longestChain != nil {
		bc.chain = longestChain
		log.Printf("Resovle confilicts replaced")
		return true
	}
	log.Printf("Resovle conflicts not replaced")
	return false
}

type TransactionRequest struct {
	SenderBlockchainAddress    *string `json:"sender_blockchain_address"`
	RecipientBlockchainAddress *string `json:"recipient_blockchain_address"`
	SenderPublicKey            *string `json:"sender_public_key"`
	Value                      *uint64 `json:"value"`
	Signature                  *string `json:"signature"`
}

func (tr *TransactionRequest) Validate() bool {
	if tr.SenderBlockchainAddress == nil ||
		tr.RecipientBlockchainAddress == nil ||
		tr.SenderPublicKey == nil ||
		tr.Value == nil ||
		tr.Signature == nil {
		return false
	}
	return true
}
